package de.hwrberlin.remo.algorithm.backwardchaining;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import de.hwrberlin.remo.algorithm.AlgorithmInitializer;
import de.hwrberlin.remo.algorithm.InferenzAlgorithm;
import de.hwrberlin.remo.algorithm.steps.AskFactStateStep;
import de.hwrberlin.remo.algorithm.steps.SetStateStep;
import de.hwrberlin.remo.algorithm.steps.ValidateRuleStep;
import de.hwrberlin.remo.model.ElementState;
import de.hwrberlin.remo.model.Fact;
import de.hwrberlin.remo.model.KnowledgeBase;
import de.hwrberlin.remo.model.KnowledgeBaseElement;
import de.hwrberlin.remo.model.LinksFromFacts;
import de.hwrberlin.remo.model.Rule;
import de.hwrberlin.remo.model.LinksFromRules;

/**
 * This class implements the backward chaining algorithm.
 * 
 * @author Mario S��nger (IT 09)
 * @version 1.0
 *
 */
public class BackwardChaining extends InferenzAlgorithm {

	private List<Rule> ruleList;
	private List<Rule> selectedGoals;
	private List<Fact> askedFacts;

	public void setGoals(List<Rule> goals) {
		this.selectedGoals = goals;
	}
	
	@Override
	public void run(KnowledgeBase base) {
		Logger logger = getLogger();
		logger.debug("Starting backward chaining algorithm");
		logger.debug("Initialize rule list");
		
		ruleList   = base.getRules();
		askedFacts = new ArrayList<Fact>();
		
		Backchain(selectedGoals);
	}
	
	private void Backchain(List<Rule> goalRuleList) {
		Logger logger = getLogger();
		
		logger.debug("Calling backchain with the following goal list: " + getRuleNameList(goalRuleList));
		
		if(goalRuleList == null) {
			logger.warn("Goal list is null");
			return;
		}

		logger.debug("Checking each goal of the goal list");
		
		for(Rule goal : goalRuleList) {
			logger.debug("Validating goal '" + goal.getName() + "'");
			logger.debug("Getting related rules to goal '" + goal.getName() + "'");
			
			// Get all rules, which have this rule as conclusion
			List<Rule> relatedRuleList = getRelatedRules(goal);
			
			ValidateRuleStep validateRuleStep = new ValidateRuleStep(goal, userInterface, editingHelper);
			validateRuleStep.perform();
			
			fireStepFinishedEvent(validateRuleStep);

			logger.debug("Validating related rules (which act as sub goals)");
			
			// Iterate over all related rules and 
			// set the related rule as goal in an
			// recursive method call
			for(Rule relatedRule : relatedRuleList) {
				logger.debug("Validating related rule / sub goal '" + relatedRule.getName() + "'");
				
//				List<Rule> ruleList = new ArrayList<Rule>();
//				ruleList.add(relatedRule);
				
				List<Rule> ruleList = new ArrayList<Rule>();
				ruleList.add(relatedRule);
				ruleList.addAll(goalRuleList);
				ruleList.remove(goal);
				
				Backchain(ruleList);
				
				logger.debug("Asking the user for all unknown facts for rule / goal '" + relatedRule.getName() + "'");
				
				// Ask user for the state of all
				// unknown facts
				askForUnkownFact(goal);
				
				// Calculate new state
				ElementState currentState = goal.checkRule();
				
				// Set new state (if new state not unkown)
				if(currentState != ElementState.UNKNOWN) {
					logger.debug("New state of rule / goal '" + goal.getName() + "' is " + currentState.getName());
					
					SetStateStep setStep = new SetStateStep(editingHelper, goal, currentState);
					setStep.perform();
					
					fireStepFinishedEvent(setStep);
					break;
				} else {
					logger.debug("New state of rule / goal '" + goal.getName() + "' is unknown");
				}
			}
			
			logger.debug("Asking the user for all unknown facts for rule / goal '" + goal.getName() + "'");
			
			// Aks user for the state of all
			// unknown facts
			askForUnkownFact(goal);
			
			// Calculate the new state
			ElementState state = goal.checkRule();
			
			if(state != goal.getState()) {
				logger.debug("New state of rule / goal '" + goal.getName() + "' is " + state.getName());
				SetStateStep setStep = new SetStateStep(editingHelper, goal, state);
				setStep.perform();
				
				fireStepFinishedEvent(setStep);
			}
		}
	} 
	
	private List<Rule> getRelatedRules(Rule element) {
		List<Rule> elements = new ArrayList<Rule>();
		
		for(Rule r : ruleList) {
			for(LinksFromRules p : r.getLinksrules()) {
				if(p.getElement() == element) {
					elements.add(r);
				}
			}
		}
		
		return elements;
	}
	
	private void askForUnkownFact(Rule rule) {
		Logger logger = getLogger();
		
		List<Fact> unknownFacts = new ArrayList<Fact>();

		logger.debug("Collecting unknown facts for rule '" + rule.getName() + "'");
		
		// Determine unknown facts
		for(LinksFromFacts premise : rule.getRelatedlinkedfacts()) {
			KnowledgeBaseElement kbElement = premise.getFact();
			
			if(kbElement instanceof Fact) {
				Fact fact = (Fact) kbElement;
				
				if(fact.getState() == ElementState.UNKNOWN) {
					unknownFacts.add(fact);
				}
			}
		}
		
		// Ask user for unknown facts
		for(Fact fact : unknownFacts) {
			
			if(! askedFacts.contains(fact)) {
				logger.debug("Asking for state of fact '" + fact.getName() + "'");
				ElementState state = userInterface.askForState(fact);
				
				if(state != null) {
					logger.debug("User answered state of '" + fact.getName() + "' is " + state.getName());
					
					AskFactStateStep askStateStep = new AskFactStateStep(fact, state, editingHelper);
					askStateStep.perform();
					
					fireStepFinishedEvent(askStateStep);
					
					askedFacts.add(fact);
				}
			}
		}
	}

	@Override
	public AlgorithmInitializer getInitializer() {
		return new BackwardChainingInitializer();
	}
	
	private String getRuleNameList(List<Rule> ruleList) {
		StringBuilder builder = new StringBuilder();

		if(ruleList != null && ruleList.size() > 0) {
			for(int i = 0; i < ruleList.size(); i++) {
				if(i > 0) {
					builder.append(",");
				}
				
				builder.append(ruleList.get(i).getName());
			}
		}
		
		return builder.toString();
	}
}